local ngx          = ngx
local require      = require
local tonumber     = tonumber
local table        = table
local math_abs     = math.abs

local log          = require("modules.commhm.log")
local auth_checker = require("modules.commhm.auth_comm_checker")
local ns_config    = require("modules.business_welfare.welfare_config")
local code_conf    = require("modules.business_welfare.welfare_code")
local business_welfare_common = require("modules.business_welfare.welfare_common")
local uu           = require("modules.commhm.utils")
local biz_auth = require("modules.business_common.business_auth")
local biz_utils = require("modules.business_common.business_utils")

local error_code = code_conf.error_code


local FOO = {
    _VERSION = "0.12.1",

    -- 不用鉴权的接口
    no_auth_func = {},

    one_share_time_out = 60 * 60, --默认缓存60分钟
    one_diff_time_out = 60 * 5,   --默认误差5分钟
}
--------------------------------------S7


FOO.ns_http_sec = auth_checker.ns_http_sec
FOO.genS2Salt = auth_checker.genS2Salt



-- 检查uin合法性 不退出
FOO.check_uin_safe = function(uin_)
    local uin = tonumber(uin_) or 0
    if uin < 1000 then
        log.error("uin error=" .. (uin_ or "nil"))
        return false
    end
    ngx.ctx.m_params.uin = uin
    return true
end


--********************************************
-- 需要进行MD5加密的参数列表 （如果参数有以下参数，则该参数需要进行md5校验）
local check_md5_param = {
    --  必带参数
    act = 1,
    time = 1,
    apiid = 1,
    ver = 1,
    uin = 1,
    s2t = 1,
    -- 通用参数 通用的名字方便其他功能直接使用
    param_id = 1,
    param_num = 1,
    -- 自定义参数
    type = 1,
    num = 1,
    buy_times = 1,
    times = 1,
    gift_id = 1,
    item_id = 1,
    buy_id = 1,
    encrypt_ver = 1,
    itemnum = 1,
    production_id = 1,
    taskidx = 1,
    skinid = 1,
    headIndex = 1,
    status = 1,
    content = 1,
    platform = 1,
    itemid = 1,
    fee_type = 1,
    value = 1,
    id = 1,
    auth = 1,
    friend_uin = 1,
    fee_id = 1,
    target = 1,
    title = 1,
    iswarehouse = 1,
    avatarUrl = 1,
    coin_num = 1,
    op_ret = 1,
    event = 1,
    prices_id = 1,
    nickname = 1,
    awardid = 1,
    price = 1,
    ishide = 1,
    cost = 1,
    ad_id = 1,
    survival = 1,
    pricetype = 1,
    itemtype = 1,
}
--*********************************************
local check_md5_param_old = {
    act = 1,
    time = 1,
    apiid = 1,
    ver = 1,
    uin = 1,
    type = 1,
    num = 1,
    buy_times = 1,
    times = 1,
    gift_id = 1,
    s2t = 1,
    buy_id = 1,
}

-- 检查命令数据的合法性
FOO.check_auth = function()
    if ngx.ctx.m_params.encrypt_ver then
        return biz_auth.check_md5_by_version()
    end
    local private_key = ns_config.business_welfare_key.act
    local param_list = {}
    for k, _ in pairs(ngx.ctx.m_params or {}) do
        if check_md5_param_old[k] or (ngx.ctx.m_params.encrypt_ver and check_md5_param[k]) then
            table.insert(param_list, k)
        end
    end

	-- 验证签名
	local auth_md5 = FOO.make_md5(ngx.ctx.m_params, param_list, private_key)
	if not auth_md5 then
		-- 参数不存在
		log.day_list( "param_check", "not auth_md5 error" )
		return false
	end

	if auth_md5 ~= (ngx.ctx.m_params.md5 or "") then
		-- 验签失败
		log.day_list( "param_check", uu.to_str(param_list), uu.to_str(auth_md5), "|", uu.to_str(ngx.ctx.m_params.auth), "|", uu.to_str(ngx.ctx.m_params.uin))
		return false
	end

	return true
end



FOO.make_md5 = function(request_list, param_list, private_key)
	table.sort(param_list, function(left, right) if left < right then return true end end)

	local base_str = FOO.pack_base_str(request_list, param_list)
	if not base_str then
		return false
	end

	local auth_md5 = ngx.md5(base_str .. private_key)
	return auth_md5
end


FOO.pack_base_str = function(request_list, param_list, code_fun)
	local base_str = false
	for i=1, #param_list do
		local key   = param_list[i]
		local value = request_list[key]
		if value then
			local code_value = value
			if code_fun then
				code_value = ngx.escape_uri(value)
			end

			if not base_str then
				base_str = key .. "=" .. code_value
			else
				base_str = base_str .. "&" .. key .. "=" .. code_value
			end
		else
			log.error(string.format("key not exist:[key=%s]", key))
			return false
		end
	end

	return base_str
end



-- cmd需要检查白名单
FOO.check_cmd_white_ip = function()

    if ns_config.debug == 1 then --测试服默认返回成功
        return true
    end

    if ns_config.cmd_white_ip["all"] and ns_config.cmd_white_ip["all"] == 1 then
        return true
    end

    local server_ip = biz_utils.real_client_ip() or "nil"
    if ns_config.cmd_white_ip[server_ip] and ns_config.cmd_white_ip[server_ip] == 1 then
        return true
    end

    return false
end


--时间校验
FOO.check_one_request_by_time = function(time_)
    local time = time_
    if time == nil then
        time = tonumber(ngx.ctx.m_params.time) or 0
    end
    local diff_time = math_abs(uu.now() - time)
    if diff_time < FOO.one_diff_time_out then
        return true --绝对值在one_diff_time_out秒内算是正常
    end
    return false, diff_time
end


--share_dict校验
FOO.check_one_request_by_judge = function(dict_name)
    local flag = table.tostring(ngx.ctx.m_params)
    local user_md5 = ngx.md5(flag)
    local flag_share = business_welfare_common.get_nginx_share(dict_name, user_md5) or {}
    if uu.tableIsEmpty(flag_share) then
        return true --在一次有效内
    end
    return false
end


--设置有效请求
FOO.set_one_request = function(dict_name)
    local flag = table.tostring(ngx.ctx.m_params)
    local user_md5 = ngx.md5(flag)
    business_welfare_common.set_nginx_share(dict_name, user_md5, {1}, FOO.one_share_time_out) --默认缓存60分钟
end


--实现逻辑 --lee 2021 08 06
--一次请求默认缓存60分钟的key, 在这个60分钟内,一次有效
--缓存失效后,根据时间来判定
--玩家倒退时间,导致与当前时间超过60分钟失效
--玩家前进时间,导致与当前时间超过60分钟也失败
--防止缓存满了后,还未到60分钟的key遭到释放,故判定的时间一定要小于缓存时间

--比如60分钟的key 在0<N<60分钟的时候遭到释放,这个时候就要靠时间去判定了
--因为如果客户端取到的是服务器时间,允许的误差在1分钟内
--如果在这1分钟内,请求成功了,那又会有60分钟的key挡住

--也许会认为有一种情况会出现刷2次的情况,就是在1分钟内请求后,key立马被释放了,然后再次发送请求,

--但实际上会被nginx下的share_dict的释放逻辑挡住,不会去释放最近的数据,所以不会出现刷2次的情况


--不是第一次请求
FOO.not_first_request = function(reason, time_)
    if ns_config.debug == 1 or ns_config.debug == 11 then-- 内网环境 跳过检测
        if ngx.ctx.m_params.test then
            local test = tonumber(ngx.ctx.m_params.test) or 0
            if test == 1 then --test == 1的时候 跳过检测
                return true
            end
        end
    end
    local time = time_
    if time == nil then
        time = tonumber(ngx.ctx.m_params.time) or 0
    end

    --需要检测的函数
    if ns_config.check_one_req and ns_config.check_one_req[ngx.ctx.m_params.act] == 1 then
        --有效请求判定
        local dict_name = ns_config.dict_name.one_request .. (ngx.ctx.m_params.uin % 10)
        --一次请求--缓存命中
        if not FOO.check_one_request_by_judge(dict_name) then
            log.debug("business_welfare_one_request|1|" .. reason .. "|" .. ngx.ctx.m_params.uin .. "|" .. ngx.ctx.m_params.act .. "|" .. time .. "|" .. uu.now() .. "|" .. uu.now() - time)
            log.day_list("business_welfare_one_request", 1, reason, ngx.ctx.m_params.uin, ngx.ctx.m_params.act, time, uu.now(), uu.now() - time)
            return false, error_code.ERROR_REQ_SAME
        else
            --缓存未命中
            --判定时间,没有超过默认阈值
            local ret, diff_time = FOO.check_one_request_by_time(time)
            if not ret then
                --时间超过了默认阈值,防止多次请求,再一次丢到缓存内
                FOO.set_one_request(dict_name)
                log.debug("business_welfare_one_request|2|" .. reason .. "|" .. ngx.ctx.m_params.uin .. "|" .. ngx.ctx.m_params.act .. "|" .. time .. "|" .. uu.now() .. "|" .. diff_time)
                log.day_list("business_welfare_one_request", 2, reason, ngx.ctx.m_params.uin, ngx.ctx.m_params.act, time, uu.now(), diff_time)
                return false, error_code.ERROR_REQ_TIMEOUT
            end
        end
        --设置有效请求key
        FOO.set_one_request(dict_name)
    end

    return true
end


-- 条件判定
FOO.condition_list = {
    --判断与转换uin为number型
    [1] = function()        --uin判定
        if FOO.check_uin_safe(ngx.ctx.m_params.uin) then
            return true
        end
        log.error("check_uin_safe fail : addr " .. ngx.ctx.xff .. "-" .. ngx.var.remote_addr .. ":" .. ngx.var.remote_port)
        return false, error_code.ERROR_PARAM
    end,
    --鉴权
    [2] = function()
        local func = ngx.ctx.m_params.act or ngx.ctx.m_params.cmd
        if func and FOO.no_auth_func[func] == 1 then
            return true
        end
        if ngx.ctx.m_params.act then
            if FOO.check_auth() then
                return true
            end
        end
        if (ns_config.debug == 1 or ns_config.debug == 11) and ngx.ctx.m_params.test and (tonumber(ngx.ctx.m_params.test) or 0) == 1 then-- 内网环境 跳过检测
            return true
        end
        log.error("check md5 fail : addr " .. ngx.ctx.xff .. "-" .. ngx.var.remote_addr .. ":" .. ngx.var.remote_port)
        return false, error_code.ERROR_AUTH
    end,
    -- 检查一次有效
    [3] = function()
        if ngx.ctx.m_params.act then
            local ret, errcode = FOO.not_first_request("act", ngx.ctx.m_params.time)
            if  not ret then
                return false, errcode
            end
        end
        return true
    end,
}


return FOO
